package gupao_edu_spring_context.context;

import gupao_edu_spring_aop.aop.config.MYAopConfig;
import gupao_edu_spring_aop.aop.framework.MYAdvisedSupport;
import gupao_edu_spring_aop.aop.framework.MYAopProxy;
import gupao_edu_spring_aop.aop.framework.MYCglibAopProxy;
import gupao_edu_spring_aop.aop.framework.MYJdkDynamicAopProxy;
import gupao_edu_spring_beans.beans.MYBeanWrapper;
import gupao_edu_spring_beans.beans.factory.MYBeanFactory;
import gupao_edu_spring_beans.beans.factory.annotation.MYAutowired;
import gupao_edu_spring_beans.beans.factory.config.MYBeanDefinition;
import gupao_edu_spring_beans.beans.factory.config.MYBeanFactoryPostProcessor;
import gupao_edu_spring_beans.beans.factory.config.MYBeanPostProcessor;
import gupao_edu_spring_beans.beans.factory.support.MYBeanDefinitionReader;
import gupao_edu_spring_beans.beans.factory.support.MYDefaultListableBeanFactory;
import gupao_edu_spring_context.stereotype.MYController;
import gupao_edu_spring_context.stereotype.MYService;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName:
 * @Description:
 * @Auther: zhouych
 * @Date: 2019/5/8 17:48
 */
public class MYApplicationContext extends MYDefaultListableBeanFactory implements MYBeanFactory {


    private String[] configLocations;

    private MYBeanDefinitionReader beanDefinitionReader;

    //单例的IOC
    private Map<String,Object> singletonObjects = new ConcurrentHashMap<String, Object>();
    //通用的IOC
    private Map<String,MYBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<String, MYBeanWrapper>();

    private final List<MYBeanPostProcessor> beanPostProcessors = new ArrayList<>();

    public MYApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
    }

    @Override
    public void refresh() throws Exception {
         //1、定位
        beanDefinitionReader = new MYBeanDefinitionReader(this.configLocations);
         //2、加载
        List<MYBeanDefinition> beanDefinitionList =  beanDefinitionReader.loadBeanDefinitions();
         //3、注册
        doRegisterBeanDefinition(beanDefinitionList);
        //4、初始化
        doInitNoLazyBeanDefinition();
    }


    /**
     * 初始化非延迟加载beanDefinition
     */
    private void doInitNoLazyBeanDefinition() {
        for (Map.Entry<String, MYBeanDefinition> entry : super.beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            if(!entry.getValue().isLazyInit()) {
                try {
                    getBean(beanName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 注册beanDefinition到容器中
     * @param beanDefinitionList
     * @throws Exception
     */
    private void doRegisterBeanDefinition(List<MYBeanDefinition> beanDefinitionList) throws Exception {
        for (MYBeanDefinition beanDefinition: beanDefinitionList) {
            if(super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())){
                throw new Exception(beanDefinition.getFactoryBeanName()+"存在");
            }
            super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(),beanDefinition);
        }
    }


    /**
     * 注入bean
     * @param beanName
     * @return
     * @throws Exception
     */
    @Override
    public Object getBean(String beanName) throws Exception {
        MYBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        //1、实例化Bean
        Object instance =instantiateBean(beanDefinition);

        //2、将Bean包装成BeanWrapper
        MYBeanWrapper wrapper = new MYBeanWrapper(instance);
        //3、将wrapper放入IOC容器中
        this.factoryBeanInstanceCache.put(beanName,wrapper);
        //4、实例化wrapper
        Object exposedObject = this.factoryBeanInstanceCache.get(beanName).getInstance();
        //5、依赖注入
        populateBean(beanName,beanDefinition,wrapper);

        //6、处理BeanWrapper
        MYBeanFactoryPostProcessor.postProcessBeanFactory(this);
        exposedObject = initializeBean(beanName, exposedObject, beanDefinition);


        return exposedObject;
    }

    @Override
    public void addBeanPostProcessor(MYBeanPostProcessor myDefaultBeanPostProcessor) {
        this.beanPostProcessors.add(myDefaultBeanPostProcessor);
    }

    private Object initializeBean(String beanName, Object bean, MYBeanDefinition beanDefinition) throws Exception {
        Object wrappedBean = bean;
        if (beanDefinition == null) {
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }

        /*try {
            invokeInitMethods(beanName, wrappedBean, beanDefinition);
        }
        catch (Throwable ex) {
            throw new Exception(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init method failed", ex);
        }*/
        if (beanDefinition == null) {
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }
        return wrappedBean;
    }



    private Object applyBeanPostProcessorsBeforeInitialization(Object wrappedBean, String beanName) throws Exception {
        Object result = wrappedBean;
        for (MYBeanPostProcessor beanProcessor : this.beanPostProcessors) {
            Object current = beanProcessor.postProcessBeforeInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    private Object applyBeanPostProcessorsAfterInitialization(Object wrappedBean, String beanName) throws Exception {
        Object result = wrappedBean;
        for (MYBeanPostProcessor beanProcessor : this.beanPostProcessors) {
            Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    /**
     * 依赖注入bean信息
     * @param beanName
     * @param beanDefinition
     * @param wrapper
     */
    private void populateBean(String beanName, MYBeanDefinition beanDefinition, MYBeanWrapper wrapper) {
        Object instance = wrapper.getInstance();

        Class<?> clazz = wrapper.getClazz();
        if(!(clazz.isAnnotationPresent(MYController.class) || clazz.isAnnotationPresent(MYService.class))){
            return;
        }

        //获得所有的fields
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if(!field.isAnnotationPresent(MYAutowired.class)){ continue;}

            MYAutowired autowired = field.getAnnotation(MYAutowired.class);

            String autowiredBeanName =  autowired.value().trim();
            if("".equals(autowiredBeanName)){
                autowiredBeanName = field.getType().getName();
            }

            //强制访问
            field.setAccessible(true);

            try {
                if(this.factoryBeanInstanceCache.get(autowiredBeanName) == null){ continue; }
                field.set(instance,this.factoryBeanInstanceCache.get(autowiredBeanName).getInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }

    }

    /**
     * 实例化对象
     * @param beanDefinition
     * @return
     */
    private Object instantiateBean(MYBeanDefinition beanDefinition) {
        //1、获取待实例化类名
        String className = beanDefinition.getBeanClassName();

        Object instance = null;
        try {
            //2、实例化对象并放入单例IOC中
            if(this.singletonObjects.containsKey(className)){
                instance = this.singletonObjects.get(className);
            }else {
                Class<?> clazz = Class.forName(className);
                instance = clazz.newInstance();

                MYAdvisedSupport advisedSupport = instantionAopConfig(beanDefinition);
                advisedSupport.setTargetClass(clazz);
                advisedSupport.setTarget(instance);

                //匹配pointCut,并创建代理对象
                if(advisedSupport.pointCutMatch()) {
                    instance = createProxy(advisedSupport).getProxy();
                }

                this.singletonObjects.put(className,instance);
                this.singletonObjects.put(beanDefinition.getFactoryBeanName(),instance);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        return instance;
    }

    private MYAopProxy createProxy(MYAdvisedSupport advisedSupport) {
        Class targetClass = advisedSupport.getTargetClass();
        if(targetClass.getInterfaces().length > 0){
            return new MYJdkDynamicAopProxy(advisedSupport);
        }
        return new MYCglibAopProxy(advisedSupport);
    }

    private MYAdvisedSupport instantionAopConfig(MYBeanDefinition beanDefinition) {
        MYAopConfig config = new MYAopConfig();
        config.setPointCut(this.beanDefinitionReader.getConfig().getProperty("pointCut"));
        config.setAspectClass(this.beanDefinitionReader.getConfig().getProperty("aspectClass"));
        config.setAspectBefore(this.beanDefinitionReader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(this.beanDefinitionReader.getConfig().getProperty("aspectAfter"));
        return new MYAdvisedSupport(config);
    }

    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new  String[this.beanDefinitionMap.size()]);
    }
}
